﻿using System.Collections.Generic;
using System;
using System.Reflection.Emit;
using ProxyViewModel.Emitors;
using System.Reflection;
using System.Collections;
using System.Threading;

namespace ProxyViewModel {
	internal class Generator {
		private readonly AssemblyBuilder _assemblyBuilder;
		private readonly IDictionary<Type, Type> _typesMap = new Dictionary<Type, Type>();
		private readonly object _lockObject = new object();

		public Generator() {
			_assemblyBuilder = DefineTypeBuilder();
		}

		private AssemblyBuilder DefineTypeBuilder(string name = "ObservablePropertiesAssembly") {
			AssemblyBuilder builder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName {
				Name = "ObservablePropertiesAssembly"
			}, AssemblyBuilderAccess.Run);
			return builder;
		}

		public Type GetProxy<T>(params object[] constructorParameters) {
			Monitor.Enter(_lockObject);
			try {
				if (!_typesMap.ContainsKey(typeof(T))) {
					GenerateType(typeof(T));
				}
				return _typesMap[typeof(T)];

			}
			finally {
				Monitor.Exit(_lockObject);
			}
		}

		private void GenerateType(Type inputType) {

#if SILVERLIGHT
			ModuleBuilder moduleBuilder = DefineTypeBuilder(inputType.GetType().Name).DefineDynamicModule(Guid.NewGuid().ToString());
#else
			ModuleBuilder moduleBuilder = _assemblyBuilder.DefineDynamicModule(Guid.NewGuid().ToString());
#endif

			TypeBuilder typeBuilder = moduleBuilder.DefineType(GenerateTypeName(inputType), TypeAttributes.Public);
			typeBuilder.SetParent(inputType);

			foreach (EmitorBase emitor in GetEmitors(typeBuilder, inputType)) {
				emitor.Emit();
			}

			_typesMap.Add(inputType, typeBuilder.CreateType());
		}

		private string GenerateTypeName(Type inputType) {
			return string.Format("{0}_{1}", inputType.Name,
				Guid.NewGuid().ToString().Replace("-", "_"));
		}

		private List<EmitorBase> GetEmitors(TypeBuilder builder, Type operatable) {
			List<EmitorBase> emitors = new List<EmitorBase>();

			Dictionary<string, object> emitorParameters = new Dictionary<string, object>();
			// the order in which emitors are added is important
			emitors.Add(new NotifyPropertyChangeEmitor(builder, operatable, emitorParameters));
			emitors.Add(new RaisePropertyChangedMethodEmitor(builder, operatable, emitorParameters));
			emitors.Add(new ContainedViewModelEmitor(builder, operatable, emitorParameters));
			emitors.Add(new DispatcherEmitor(builder, operatable, emitorParameters));
			emitors.Add(new PropertyValidationEmitor(builder, operatable, emitorParameters));
			emitors.Add(new PropertyOverrideEmitor(builder, operatable, emitorParameters));
			emitors.Add(new ValidatableViewModelEmitor(builder, operatable, emitorParameters));
			emitors.Add(new CommandsContainerEmitor(builder, operatable, emitorParameters));
			emitors.Add(new CommandsEmitor(builder, operatable, emitorParameters));
			emitors.Add(new ConstructorsEmitor(builder, operatable, emitorParameters));

			return emitors;
		}
	}
}
